using System;
using System.Collections.Generic;
using System.Reflection;
using Kopernikus.Collections.Generic;
using Kopernikus.Parsing.Earley;
using Kopernikus.Parsing.GenericLexing;
using Kopernikus.Parsing.GenericParsing;
using Kopernikus.Parsing.Utility;

namespace Kopernikus.Parsing.GenericActions
{
    public class GenericEvaluator<TParser, S> : IEarleyEvaluator<GenericMatch<S>, S>
    {
        private static readonly S[] EmptyReductionArguments = new S[0];
        private readonly TParser m_instance;

        private readonly Dictionary<Production, int> m_productionPriorities;
        private readonly Dictionary<Production, ISemanticAction<TParser, S>> m_reduceActions;
        private IEarleyParser<GenericMatch<S>, S> m_parser;

        public GenericEvaluator()
        {
            m_productionPriorities = new Dictionary<Production, int>();
            m_reduceActions = new Dictionary<Production, ISemanticAction<TParser, S>>();
            m_parser = null;
            m_instance = default(TParser);
            Initialize(typeof (S));
        }

        private GenericEvaluator(TParser instance, GenericEvaluator<TParser, S> template)
        {
            m_instance = instance;
            m_productionPriorities = template.m_productionPriorities;
            m_reduceActions = template.m_reduceActions;
            m_parser = template.Parser;
        }

        public Dictionary<Production, ISemanticAction<TParser, S>> ReduceActions
        {
            get { return m_reduceActions; }
        }

        public Dictionary<Production, int> ProductionPriorities
        {
            get { return m_productionPriorities; }
        }

        public IGrammar Grammar
        {
            get { return Parser.Grammar; }
        }

        public IEarleyParser<GenericMatch<S>, S> Parser
        {
            get { return m_parser; }
        }

        public TParser TargetInstance
        {
            get { return m_instance; }
        }

        #region IEarleyEvaluator<GenericMatch<S>,S> Members

        public Production Disambiguate(Set<Production> productions)
        {
            Production bestProduction = null;

            IDisambiguator disambiguator = m_instance as IDisambiguator;
            if (disambiguator != null)
            {
                bestProduction = disambiguator.Disambiguate(productions);
                if (bestProduction != null)
                {
                    return bestProduction;
                }
            }

            int bestPrio = 0;

            foreach (Production production in productions)
            {
                int prio;
                if (ProductionPriorities.TryGetValue(production, out prio))
                {
                    if ((prio < bestPrio) || (bestProduction == null))
                    {
                        bestPrio = prio;
                        bestProduction = production;
                    }
                }
            }
            return bestProduction;
        }

        public S ReduceEmpty(Symbol symbol)
        {
            Production p = Grammar.ProductionSets.GetNullableProductionByLHS(symbol);

            if (p.RHS.Length == 0)
            {
                return
                    Reduce(Grammar.ProductionSets.GetNullableProductionByLHS(symbol),
                           EmptyReductionArguments);
            }

            S[] reductions = new S[p.RHS.Length];
            for (int i = 0; i < p.RHS.Length; ++i)
            {
                reductions[i] = ReduceEmpty(p.RHS[i]);
            }
            return Reduce(Grammar.ProductionSets.GetNullableProductionByLHS(symbol), reductions);
        }

        public S Shift(GenericMatch<S> token)
        {
            return token.Value;
        }

        public S Reduce(Production p, S[] rhs)
        {
            return
                ReduceActions[Grammar.MapProduction(p)].Invoke(m_instance, rhs);
        }

        #endregion

        public GenericEvaluator<TParser, S> Clone(TParser instance)
        {
            return new GenericEvaluator<TParser, S>(instance, this);
        }

        private void Initialize(Type actionReturnType)
        {
            Builder builder = new Builder();

            List<Production> productionsAddedInOrderOfDeclaration = new List<Production>();

            foreach (MethodInfo method in typeof (TParser).GetMethods(
                BindingFlags.Public | BindingFlags.NonPublic |
                BindingFlags.Instance | BindingFlags.Static |
                BindingFlags.InvokeMethod))
            {
                foreach (CfgAttribute cfgAttribute in method.GetCustomAttributes(typeof (CfgAttribute), true))
                {
                    if (!actionReturnType.IsAssignableFrom(method.ReturnType))
                    {
                        throw new InvalidSemanticActionException(method, typeof (S));
                    }

                    builder.AddReduceMethod(method, cfgAttribute.Production, cfgAttribute.ParametersFixup);
                    builder.AddReturnTypeForReduction(method, cfgAttribute.Production);
                    builder.AddProductionToGrammar(cfgAttribute.Production, cfgAttribute is CfgStartAttribute);

                    productionsAddedInOrderOfDeclaration.Add(cfgAttribute.Production);
                }

                // Take care of known terminal symbols
                foreach (
                    LexSymbolAttribute lexSymbolAttribute in
                        method.GetCustomAttributes(typeof (LexSymbolAttribute), true))
                {
                    builder.AddReturnTypeForReduction(method, new Symbol(lexSymbolAttribute.Symbol));
                }

                // Take care of default known terminal symbols
                foreach (
                    LexDefaultAttribute lexDefaultAttribute in
                        method.GetCustomAttributes(typeof (LexDefaultAttribute), true))
                {
                    if (lexDefaultAttribute.LiteralTerminals == null)
                    {
                        builder.UseSingleCharLiteralSymbolReductions = true;
                    }
                    else
                    {
                        foreach (string literal in lexDefaultAttribute.LiteralTerminals)
                        {
                            builder.AddReturnTypeForReduction(method, new Symbol(literal));
                        }
                    }
                }


            }

            IGrammar grammar = builder.GrammarBuilder.CreateGrammar();

            foreach (
                KeyValuePair<Production, Pair<MethodInfo, ISemanticActionParametersFixup>> pair in builder.ReduceMethods
                )
            {
                Production production = pair.Key;
                if (!grammar.Productions.Contains(production))
                {
                    continue;
                }
                MethodInfo method = pair.Value.First;
                ISemanticActionParametersFixup fixup = pair.Value.Second;

                ISemanticAction<TParser, S> action = CreateSemanticAction(builder, production, method, fixup);

                m_reduceActions[production] = action;
            }

            productionsAddedInOrderOfDeclaration.Reverse();
            foreach (Production production in productionsAddedInOrderOfDeclaration)
            {
                if (!m_productionPriorities.ContainsKey(production))
                {
                    if (grammar.Productions.Contains(production))
                    {
                        m_productionPriorities.Add(production, m_productionPriorities.Count);
                    }
                }
            }

            m_parser =
                new EarleyParser<GenericMatch<S>, S>(builder.GrammarBuilder.CreateGrammar(),
                                                     EarleyParserOptions.Precompile);
        }

        private static ISemanticAction<TParser, S> CreateSemanticAction(Builder builder, Production production,
                                                                        MethodInfo method,
                                                                        ISemanticActionParametersFixup fixup)
        {
            ParameterInfo[] parameters = method.GetParameters();

            if (parameters.Length == 0)
            {
                if (fixup.HasFixups)
                {
                    throw new InvalidSemanticActionException(
                        method, typeof (S));
                }
                return new ReflectedSemanticActionWithNoParameters<TParser, S>(method);
            }

            if ((parameters.Length == 1) && (parameters[0].ParameterType.IsArray))
            {
                Type arrayType = parameters[0].ParameterType;
                if (arrayType.IsArray)
                {
                    Type elementType = arrayType.GetElementType();
                    foreach (Symbol symbol in production.RHS)
                    {
                        if (!builder.IsValidParameterTypeForSymbol(symbol, elementType))
                        {
                            throw new InvalidSemanticActionException(method, typeof (S));
                        }
                    }
                    return
                        new ReflectedSemanticActionWithArrayParameter<TParser, S>(method, arrayType.GetElementType(),
                                                                                  fixup);
                }
            }

            Symbol[] fixupRHS = fixup.Fixup(production.RHS);
            ParameterInfo[] fixupParameters = parameters;
            if (fixupRHS.Length == fixupParameters.Length)
            {
                for (int i = 0; i < fixupParameters.Length; ++i)
                {
                    if (!builder.IsValidParameterTypeForSymbol(fixupRHS[i], fixupParameters[i].ParameterType))
                    {
                        throw new InvalidSemanticActionException(method, typeof (S));
                    }
                }
                return new ReflectedSemanticAction<TParser, S>(method, fixup);
            }
            throw new InvalidSemanticActionException(method, typeof (S));
        }

        #region Nested type: Builder

        private class Builder
        {
            private readonly GrammarBuilder m_grammarBuilder = new GrammarBuilder();

            private readonly Dictionary<Production, Pair<MethodInfo, ISemanticActionParametersFixup>> m_reduceMethods =
                new Dictionary<Production, Pair<MethodInfo, ISemanticActionParametersFixup>>();

            private readonly Dictionary<Symbol, Set<Type>> m_returnTypesForReductionSymbol =
                new Dictionary<Symbol, Set<Type>>();

            private bool m_useSingleCharLiteralSymbolReductions;

            public Dictionary<Production, Pair<MethodInfo, ISemanticActionParametersFixup>> ReduceMethods
            {
                get { return m_reduceMethods; }
            }

            public Dictionary<Symbol, Set<Type>> ReturnTypesForReductionSymbol
            {
                get { return m_returnTypesForReductionSymbol; }
            }

            public GrammarBuilder GrammarBuilder
            {
                get { return m_grammarBuilder; }
            }

            public bool UseSingleCharLiteralSymbolReductions
            {
                get { return m_useSingleCharLiteralSymbolReductions; }
                set { m_useSingleCharLiteralSymbolReductions = value; }
            }

            public bool IsValidParameterTypeForSymbol(Symbol symbol, Type parameterType)
            {
                Set<Type> returnTypes;
                if (m_returnTypesForReductionSymbol.TryGetValue(symbol, out returnTypes))
                {
                    foreach (Type returnType in returnTypes)
                    {
                        if (!parameterType.IsAssignableFrom(returnType))
                        {
                            return false;
                        }
                    }
                    return true;
                }
                if (UseSingleCharLiteralSymbolReductions)
                {
                    if (symbol.Name.Length == 1)
                    {
                        return parameterType.IsAssignableFrom(typeof (S));
                    }
                }
                return false;
            }

            public void AddReduceMethod(MethodInfo method, Production production, ISemanticActionParametersFixup fixup)
            {
                if (ReduceMethods.ContainsKey(production))
                {
                    throw new InvalidSemanticActionException(method, typeof (S));
                }
                ReduceMethods.Add(production, Pair<MethodInfo, ISemanticActionParametersFixup>.Create(method, fixup));
            }

            public void AddReturnTypeForReduction(MethodInfo method, Symbol lhs)
            {
                Set<Type> returnTypes;
                if (!ReturnTypesForReductionSymbol.TryGetValue(lhs, out returnTypes))
                {
                    returnTypes = new Set<Type>();
                    ReturnTypesForReductionSymbol.Add(lhs, returnTypes);
                }
                returnTypes.Add(method.ReturnType);
            }

            public void AddReturnTypeForReduction(MethodInfo method, Production production)
            {
                AddReturnTypeForReduction(method, production.LHS);
            }

            public void AddProductionToGrammar(Production production, bool isStartProduction)
            {
                m_grammarBuilder.Productions.Add(production);
                if (isStartProduction)
                {
                    m_grammarBuilder.StartProductions.Add(production);
                }
            }
        }

        #endregion
    }
}